﻿#ifndef ___PCLIB_S_SHA512___
#define ___PCLIB_S_SHA512___


#include "../m_core/m_core.hpp"
#include "s_crypto_base.hpp"

#define PCSHA512_LEN 64
#define PCSHA384_LEN 48

/*
 * Round constants
 */
static const uint64_t PCSHA512_K[80] =
{
    (0x428A2F98D728AE22ULL),  (0x7137449123EF65CDULL),
    (0xB5C0FBCFEC4D3B2FULL),  (0xE9B5DBA58189DBBCULL),
    (0x3956C25BF348B538ULL),  (0x59F111F1B605D019ULL),
    (0x923F82A4AF194F9BULL),  (0xAB1C5ED5DA6D8118ULL),
    (0xD807AA98A3030242ULL),  (0x12835B0145706FBEULL),
    (0x243185BE4EE4B28CULL),  (0x550C7DC3D5FFB4E2ULL),
    (0x72BE5D74F27B896FULL),  (0x80DEB1FE3B1696B1ULL),
    (0x9BDC06A725C71235ULL),  (0xC19BF174CF692694ULL),
    (0xE49B69C19EF14AD2ULL),  (0xEFBE4786384F25E3ULL),
    (0x0FC19DC68B8CD5B5ULL),  (0x240CA1CC77AC9C65ULL),
    (0x2DE92C6F592B0275ULL),  (0x4A7484AA6EA6E483ULL),
    (0x5CB0A9DCBD41FBD4ULL),  (0x76F988DA831153B5ULL),
    (0x983E5152EE66DFABULL),  (0xA831C66D2DB43210ULL),
    (0xB00327C898FB213FULL),  (0xBF597FC7BEEF0EE4ULL),
    (0xC6E00BF33DA88FC2ULL),  (0xD5A79147930AA725ULL),
    (0x06CA6351E003826FULL),  (0x142929670A0E6E70ULL),
    (0x27B70A8546D22FFCULL),  (0x2E1B21385C26C926ULL),
    (0x4D2C6DFC5AC42AEDULL),  (0x53380D139D95B3DFULL),
    (0x650A73548BAF63DEULL),  (0x766A0ABB3C77B2A8ULL),
    (0x81C2C92E47EDAEE6ULL),  (0x92722C851482353BULL),
    (0xA2BFE8A14CF10364ULL),  (0xA81A664BBC423001ULL),
    (0xC24B8B70D0F89791ULL),  (0xC76C51A30654BE30ULL),
    (0xD192E819D6EF5218ULL),  (0xD69906245565A910ULL),
    (0xF40E35855771202AULL),  (0x106AA07032BBD1B8ULL),
    (0x19A4C116B8D2D0C8ULL),  (0x1E376C085141AB53ULL),
    (0x2748774CDF8EEB99ULL),  (0x34B0BCB5E19B48A8ULL),
    (0x391C0CB3C5C95A63ULL),  (0x4ED8AA4AE3418ACBULL),
    (0x5B9CCA4F7763E373ULL),  (0x682E6FF3D6B2B8A3ULL),
    (0x748F82EE5DEFB2FCULL),  (0x78A5636F43172F60ULL),
    (0x84C87814A1F0AB72ULL),  (0x8CC702081A6439ECULL),
    (0x90BEFFFA23631E28ULL),  (0xA4506CEBDE82BDE9ULL),
    (0xBEF9A3F7B2C67915ULL),  (0xC67178F2E372532BULL),
    (0xCA273ECEEA26619CULL),  (0xD186B8C721C0C207ULL),
    (0xEADA7DD6CDE0EB1EULL),  (0xF57D4F7FEE6ED178ULL),
    (0x06F067AA72176FBAULL),  (0x0A637DC5A2C898A6ULL),
    (0x113F9804BEF90DAEULL),  (0x1B710B35131C471BULL),
    (0x28DB77F523047D84ULL),  (0x32CAAB7B40C72493ULL),
    (0x3C9EBE0A15C9BEBCULL),  (0x431D67C49C100D4CULL),
    (0x4CC5D4BECB3E42B6ULL),  (0x597F299CFC657E2AULL),
    (0x5FCB6FAB3AD6FAECULL),  (0x6C44198C4A475817ULL)
};


/**
*@brief        SHA512/SHA384计算
*/
class CPCSHA512: CPCNoCopyable
{
public:
    explicit CPCSHA512(bool is384 = false){ Init(is384); }

    /**
    *@brief     直接获取字节或字符串的SHA512/SHA384
    *@param     src    [in]    输入的数据
    *@param     len    [in]    数据长度
    *@param     is384  [in]    true：SHA384；false：SHA512
    *@return    64/48字节的SHA512/SHA384
    */
    static std::string GetBytesSHA512(const void* src, size_t len, bool is384 = false)
    {
        CPCSHA512 sha512ctx(is384);
        sha512ctx.Update(src, len);
        return sha512ctx.Finish();
    }

    /**
    *@brief     直接获取字节或字符串的sha512 hmac
    *@param     src[in]         输入的数据
    *@param     srclen[in]      数据长度
    *@param     keybytes[in]    密钥
    *@param     keylen[in]      密钥长度
    *@return    64字节的sha512 hmac
    */
    static std::string GetBytesSHA512HMAC (const void *src, size_t srclen,const void *keybytes, size_t keylen)
    {
        return PCGetBytesHMAC<CPCSHA512>(src,srclen,keybytes,keylen,128,PCSHA512_LEN);
    }

public:
    /**
    * @brief 初始化
    * @param is384  true：SHA384；false：SHA512
    */
    void Init(bool is384 = false)
    {
        memset( m_total, 0, sizeof( m_total ) );
        memset( m_state, 0, sizeof( m_state ) );
        memset( m_buffer,0, sizeof( m_buffer) );
        m_is384 = is384;

        if( !m_is384 )
        {
            /* SHA-512 */
            m_state[0] = (0x6A09E667F3BCC908ULL);
            m_state[1] = (0xBB67AE8584CAA73BULL);
            m_state[2] = (0x3C6EF372FE94F82BULL);
            m_state[3] = (0xA54FF53A5F1D36F1ULL);
            m_state[4] = (0x510E527FADE682D1ULL);
            m_state[5] = (0x9B05688C2B3E6C1FULL);
            m_state[6] = (0x1F83D9ABFB41BD6BULL);
            m_state[7] = (0x5BE0CD19137E2179ULL);
        }
        else
        {
            /* SHA-384 */
            m_state[0] = (0xCBBB9D5DC1059ED8ULL);
            m_state[1] = (0x629A292A367CD507ULL);
            m_state[2] = (0x9159015A3070DD17ULL);
            m_state[3] = (0x152FECD8F70E5939ULL);
            m_state[4] = (0x67332667FFC00B31ULL);
            m_state[5] = (0x8EB44A8768581511ULL);
            m_state[6] = (0xDB0C2E0D64F98FA7ULL);
            m_state[7] = (0x47B5481DBEFA4FA4ULL);
        }
    }

    /**
    * @brief Update
    *
    * @param src    Buffer
    * @param ilen   Length
    */
    void Update(const void *src, size_t ilen )
    {
        if( ilen == 0 || src == NULL)
            return;
        const unsigned char * input = (const unsigned char *) src;
        unsigned int left = (unsigned int) (m_total[0] & 0x7F);
        size_t fill = 128 - left;

        m_total[0] += (uint64_t) ilen;

        if( m_total[0] < (uint64_t) ilen )
            m_total[1]++;

        if( left && ilen >= fill )
        {
            memcpy( (void *) (m_buffer + left), input, fill );

            InternalProcess512( m_buffer ) ;

            input += fill;
            ilen  -= fill;
            left = 0;
        }

        while( ilen >= 128 )
        {
            InternalProcess512( input ) ;

            input += 128;
            ilen  -= 128;
        }

        if( ilen > 0 )
            memcpy( (void *) (m_buffer + left), input, ilen );
    }

    /**
    * @brief 计算结果
    */
    std::string Finish()
    {
    //////////////////////////////////////////////////////////////////
    #define PCSHA512_PUT_UINT64_BE(n,b,i)                            \
    {                                                       \
        (b)[(i)    ] = (unsigned char) ( (n) >> 56 );       \
        (b)[(i) + 1] = (unsigned char) ( (n) >> 48 );       \
        (b)[(i) + 2] = (unsigned char) ( (n) >> 40 );       \
        (b)[(i) + 3] = (unsigned char) ( (n) >> 32 );       \
        (b)[(i) + 4] = (unsigned char) ( (n) >> 24 );       \
        (b)[(i) + 5] = (unsigned char) ( (n) >> 16 );       \
        (b)[(i) + 6] = (unsigned char) ( (n) >>  8 );       \
        (b)[(i) + 7] = (unsigned char) ( (n)       );       \
    }
    //////////////////////////////////////////////////////////////////
        int ret = -1;
        unsigned used;
        uint64_t high, low;
        unsigned char output[PCSHA512_LEN] ;

        /*
         * Add padding: 0x80 then 0x00 until 16 bytes remain for the length
         */
        used = m_total[0] & 0x7F;

        m_buffer[used++] = 0x80;

        if( used <= 112 )
        {
            /* Enough room for padding + length in current block */
            memset( m_buffer + used, 0, 112 - used );
        }
        else
        {
            /* We'll need an extra block */
            memset( m_buffer + used, 0, 128 - used );
            InternalProcess512(  m_buffer );
            memset( m_buffer, 0, 112 );
        }

        /*
         * Add message length
         */
        high = ( m_total[0] >> 61 )
             | ( m_total[1] <<  3 );
        low  = ( m_total[0] <<  3 );

        PCSHA512_PUT_UINT64_BE( high, m_buffer, 112 );
        PCSHA512_PUT_UINT64_BE( low,  m_buffer, 120 );

        InternalProcess512(  m_buffer );

        /*
         * Output final state
         */
        PCSHA512_PUT_UINT64_BE( m_state[0], output,  0 );
        PCSHA512_PUT_UINT64_BE( m_state[1], output,  8 );
        PCSHA512_PUT_UINT64_BE( m_state[2], output, 16 );
        PCSHA512_PUT_UINT64_BE( m_state[3], output, 24 );
        PCSHA512_PUT_UINT64_BE( m_state[4], output, 32 );
        PCSHA512_PUT_UINT64_BE( m_state[5], output, 40 );


        if( !m_is384 )
        {
            PCSHA512_PUT_UINT64_BE( m_state[6], output, 48 );
            PCSHA512_PUT_UINT64_BE( m_state[7], output, 56 );
        }
        return std::string( (const char*)output, m_is384? PCSHA384_LEN: PCSHA512_LEN);
    //////////////////////////////////////////////////////////////////
    #undef PCSHA512_PUT_UINT64_BE
    //////////////////////////////////////////////////////////////////
    }

protected:
    void InternalProcess512( const unsigned char data[128] )
    {
    //////////////////////////////////////////////////////////////////
    #define PCSHA512_GET_UINT64_BE(n,b,i)                            \
    {                                                       \
        (n) = ( (uint64_t) (b)[(i)    ] << 56 )       \
            | ( (uint64_t) (b)[(i) + 1] << 48 )       \
            | ( (uint64_t) (b)[(i) + 2] << 40 )       \
            | ( (uint64_t) (b)[(i) + 3] << 32 )       \
            | ( (uint64_t) (b)[(i) + 4] << 24 )       \
            | ( (uint64_t) (b)[(i) + 5] << 16 )       \
            | ( (uint64_t) (b)[(i) + 6] <<  8 )       \
            | ( (uint64_t) (b)[(i) + 7]       );      \
    }
    #define PCSHA512_SHR(x,n) ((x) >> (n))
    #define PCSHA512_ROTR(x,n) (PCSHA512_SHR((x),(n)) | ((x) << (64 - (n))))

    #define PCSHA512_S0(x) (PCSHA512_ROTR(x, 1) ^ PCSHA512_ROTR(x, 8) ^  PCSHA512_SHR(x, 7))
    #define PCSHA512_S1(x) (PCSHA512_ROTR(x,19) ^ PCSHA512_ROTR(x,61) ^  PCSHA512_SHR(x, 6))

    #define PCSHA512_S2(x) (PCSHA512_ROTR(x,28) ^ PCSHA512_ROTR(x,34) ^ PCSHA512_ROTR(x,39))
    #define PCSHA512_S3(x) (PCSHA512_ROTR(x,14) ^ PCSHA512_ROTR(x,18) ^ PCSHA512_ROTR(x,41))

    #define PCSHA512_F0(x,y,z) (((x) & (y)) | ((z) & ((x) | (y))))
    #define PCSHA512_F1(x,y,z) ((z) ^ ((x) & ((y) ^ (z))))

    #define PCSHA512_P(a,b,c,d,e,f,g,h,x,K)                                  \
        do                                                          \
        {                                                           \
            temp1 = (h) + PCSHA512_S3(e) + PCSHA512_F1((e),(f),(g)) + (K) + (x);      \
            temp2 = PCSHA512_S2(a) + PCSHA512_F0((a),(b),(c));                        \
            (d) += temp1; (h) = temp1 + temp2;                      \
        } while( 0 )
    //////////////////////////////////////////////////////////////////

        int i;
        uint64_t temp1, temp2, W[80];
        uint64_t A[8];

        for( i = 0; i < 8; i++ )
            A[i] = m_state[i];

        for( i = 0; i < 16; i++ )
        {
            PCSHA512_GET_UINT64_BE( W[i], data, i << 3 );
        }

        for( ; i < 80; i++ )
        {
            W[i] = PCSHA512_S1(W[i -  2]) + W[i -  7] +
                   PCSHA512_S0(W[i - 15]) + W[i - 16];
        }

        i = 0;
        do
        {
            PCSHA512_P( A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[5], A[6], A[7], A[0], A[1], A[2], A[3], A[4], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[4], A[5], A[6], A[7], A[0], A[1], A[2], A[3], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], W[i], PCSHA512_K[i] ); i++;
            PCSHA512_P( A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], W[i], PCSHA512_K[i] ); i++;
        }
        while( i < 80 );

        for( i = 0; i < 8; i++ )
            m_state[i] += A[i];
    //////////////////////////////////////////////////////////////////
    #undef PCSHA512_GET_UINT64_BE
    #undef PCSHA512_SHR
    #undef PCSHA512_ROTR
    #undef PCSHA512_S0
    #undef PCSHA512_S1
    #undef PCSHA512_S2
    #undef PCSHA512_S3
    #undef PCSHA512_F0
    #undef PCSHA512_F1
    #undef PCSHA512_P
    //////////////////////////////////////////////////////////////////
    }
    uint64_t m_total[2];          /*!< The number of Bytes processed. */
    uint64_t m_state[8];          /*!< The intermediate digest state. */
    unsigned char m_buffer[128];  /*!< The data block being processed. */
    bool m_is384;                  /*!< Determines which function to use:
                                     0: Use SHA-512, or 1: Use SHA-384. */
};



#endif    //___PCLIB_S_SHA512___